use crate::{
    block_cache_dev::BlockCacheDevice,
    BlockID, 
    BLOCK_SIZE
};

const BIT_SIZE_A_BYTE: usize = 8;
const BIT_SIZE_A_BLOCK: usize = BLOCK_SIZE * BIT_SIZE_A_BYTE;
const U8_SIZE_A_BLOCK: usize = BLOCK_SIZE;

type BitmapBlock = [u8; U8_SIZE_A_BLOCK];

pub struct Bitmap {
    start_block_id: BlockID,
    block_size: usize,
}

impl Bitmap {
    pub fn new(start_block_id: BlockID, block_size: usize) -> Self {
        Self {
            start_block_id, block_size
        }
    }
}

impl Bitmap {
    pub fn alloc(&mut self, device: &mut BlockCacheDevice) -> Option<usize> {
        // Find first not 0x1
        let mut blcok_id = self.start_block_id;
        for block_index in 0..self.block_size {
            let datas: &mut BitmapBlock = device.get_mut(blcok_id, 0);
            for (byte_index, byte) in datas.iter_mut().enumerate() {
                if *byte != std::u8::MAX {
                    // Find 0x0!
                    let mut copy_byte = *byte;
                    let mut index = 0;
                    loop {
                        if (copy_byte & 0x1u8) == 0 {
                            *byte |= 0x1u8 << index;
                            return Some(block_index * BIT_SIZE_A_BLOCK + byte_index * BIT_SIZE_A_BYTE + index);
                        }
                        else {
                            index += 1;
                            copy_byte >>= 1;
                        }
                    }
                }
            }
            blcok_id += 1;
        }

        None
    }

    pub fn dealloc(&mut self, index: usize, device: &mut BlockCacheDevice) {
        let block_index = index / BLOCK_SIZE;
        let block_inner_index = index % BLOCK_SIZE;
        let byte_index =  block_inner_index / BIT_SIZE_A_BYTE;
        let byte_offset = block_inner_index % BIT_SIZE_A_BYTE;

        let block_id = self.start_block_id + block_index as BlockID;
        let datas: &mut BitmapBlock = device.get_mut(block_id, 0);
        let byte: &mut u8 = &mut datas[byte_index];
        *byte &= 0x1u8 << byte_offset;
    }

    pub fn manage_size(&self) -> usize {
        self.block_size * BIT_SIZE_A_BLOCK
    }
}